<?php

namespace App\Common\Util;

use App\Common\Enums\Base\RouteEnumType;
use App\Common\Model\Base\Route;
use Sc\Util\Tool;
use think\facade\App;

/**
 * 插件安装助手
 *
 * Class PluginsHelper
 */
class PluginsInstallHelper
{
    /**
     * 添加代理， 代理类的命名空间须和源类保持一致
     * 如果代理类需要继承源类 需执行 {@link self::cloneOrigin()},不继承则复制源代码修改即可
     * 继承的类名为：Origin\源类名的全命名,
     * 例：代理 App\Service\Admin\Base\DefaultLoginService
     *      1.在 Install::run() 里面添加 PluginsInstallHelper::addProxy(app\Service\Admin\Base\DefaultLoginService::class, 'proxyClasspath')
     *      2.PluginsInstallHelper::cloneOrigin("DefaultLoginServiceFilePath")
     *      1.在 Install::run() 里面添加 PluginsInstallHelper::addProxy("Origin\\" . App\Service\Admin\Base\DefaultLoginService::class, 'proxyClasspath')
     *      2.proxyClass extends Origin\app\Service\Admin\Base\DefaultLoginService{}
     *      3.增加 listener 监听事件 BeforeWorkerStart 增加 PluginsInstallHelper::cloneOrigin("DefaultLoginServiceFilePath")
     *
     * @param string $originClass 代理的源类
     * @param string $proxyClassPath  代理类路劲
     *
     * @return void
     * @throws \ReflectionException
     */
    public static function addProxy(string $originClass, string $proxyClassPath): void
    {
        $plugins       = App::getConfigPath() . '/plugins.php';
        $pluginsConfig = include $plugins;
        if (isset($pluginsConfig['proxy'][$originClass])) {
            TP::commandOutput()->error("已存在{$originClass}的代理映射，跳过执行");
            return;
        }

        $rootPath = App::getRootPath();
        $code     = file_get_contents($plugins);
        $path     = strtr($proxyClassPath, [$rootPath => '']);

        $newCode = preg_replace('/(\'proxy\' *=> *\[)[\r\n\s]*(.*?)[\r\n\s]*(\])/s',
            "$1\n        $2\n        '$originClass' => '$path',\n    $3", $code);

        file_put_contents($plugins, $newCode);
    }

    /**
     * 克隆需要被代理的类， 针对代理类需要继承源类的情况
     *
     * @param string $classPath 源类文件路径
     *
     * @return string 返回克隆后的文件相对根目录的路径
     */
    public static function cloneOrigin(string $classPath): string
    {
        $code    = file_get_contents($classPath);
        preg_match('/namespace (.*);/', $code, $match);

        $classNamespace = strtr($match[1], ['\\' => '_']);
        $filename       = basename($classPath);
        $filepath       = "/runtime/plugins/ProxyOriginClass/{$classNamespace}_{$filename}.php";

        if (file_exists(App::getRootPath() . $filepath)) {
            return $filepath;
        }

        if (!is_dir($dir = dirname(App::getRootPath() . $filepath))) {
            mkdir($dir, 0777, true);
        }

        $newCode = preg_replace('/namespace (.*);/', 'namespace Origin\\\$1;', $code);
        file_put_contents(App::getRootPath() . $filepath, $newCode);

        return $filepath;
    }

    /**
     * 引入外部资源，如css,js
     *
     * @param string $path   文件地址
     * @param string $networkUrl 基于域名访问网络访问地址（既不要域名） 例：/static/test.js
     * @return void
     */
    public static function addResource(string $path, string $networkUrl): void
    {
        $networkFileUrl = App::getRootPath() . '/public' . $networkUrl;
        is_dir($dir = dirname($networkFileUrl)) or mkdir($dir, 0777, true);

        if (!is_file($networkFileUrl)) {
            copy($path, $networkFileUrl);
        }else{
            echo "文件已存在：$networkUrl\n";
        }
    }

    /**
     * 批量引入资源
     *
     * @param string $path 资源文件夹
     * @param string $networkPath
     *
     * @return void
     * @throws \Exception
     */
    public static function batchAddResource(string $path, string $networkPath): void
    {
        if (is_dir($networkPath)) {
            TP::commandOutput()->info("文件夹已存在：$networkPath");
            return;
        }
        Tool::dir($path)->copyTo(App::getRootPath() . 'public/' . $networkPath);
    }

    /**
     * 清理资源
     *
     * @param string $path
     *
     * @return void
     */
    public function clearResource(string $path): void
    {
        if (is_file($path)) {
            $res = unlink($path);
        }else{
            $res = Tool::dir($path)->remove();
        }

        TP::commandOutput()->info($res ? "资源清理完毕" : ("资源清理失败：" . $path));
    }

    /**
     * 删除代理类
     *
     * @param string $originClass    类全名
     * @param string $proxyClassPath 代理类地址
     *
     * @return void
     */
    public static function delProxy(string $originClass, string $proxyClassPath): void
    {
        $plugins       = App::getConfigPath() . '/plugins.php';

        $rootPath = App::getRootPath();
        $code     = file_get_contents($plugins);
        $path     = strtr($proxyClassPath, [$rootPath => '']);

        $newCode = preg_replace("/'$originClass'\s*=>\s*'$path'\s*,/", '', $code);

        file_put_contents($plugins, $newCode);
    }


    /**
     * 添加菜单
     *
     * @param string     $title
     * @param string     $route
     * @param string     $icon
     * @param string|int $parent
     * @param int        $sort
     *
     * @return string
     */
    public static function addMenu(string $title, string $route, string $icon, string|int $parent, int $sort = 1): string
    {
        $pid = is_int($parent) ? $parent : Route::where('title', $parent)->value('id', 0);

        if (Route::where('title', $title)->where('pid', $pid)->value('id')) {
            return "菜单已存在\r\n";
        }

        Route::create([
            'pid'         => $pid,
            'type'        => RouteEnumType::LeftMenu->value,
            'title'       => $title,
            'icon'        => $icon,
            'route'       => $route,
            'weigh'       => $sort,
        ]);

        return "菜单创建成功\r\n";
    }
}